/** * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.brixcms.plugin.hierarchical.admin; import java.util.Arrays; import java.util.Collection; import javax.jcr.ItemNotFoundException; import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; import org.apache.wicket.MetaDataKey; import org.apache.wicket.extensions.markup.html.tree.BaseTree; import org.apache.wicket.extensions.markup.html.tree.DefaultTreeState; import org.apache.wicket.extensions.markup.html.tree.ITreeState; import org.apache.wicket.extensions.markup.html.tree.LinkTree; import org.apache.wicket.extensions.markup.html.tree.LinkType; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; import org.brixcms.BrixNodeModel; import org.brixcms.auth.Action.Context; import org.brixcms.jcr.JcrUtil; import org.brixcms.jcr.api.JcrSession; import org.brixcms.jcr.exception.JcrException; import org.brixcms.jcr.wrapper.BrixNode; import org.brixcms.plugin.hierarchical.HierarchicalNodePlugin; import org.brixcms.plugin.hierarchical.HierarchicalPluginLocator; import org.brixcms.plugin.hierarchical.NodeChildFilter; import org.brixcms.plugin.site.NodeTreeRenderer; import org.brixcms.plugin.site.SimpleCallback; import org.brixcms.web.generic.BrixGenericPanel; import org.brixcms.web.picker.common.TreeAwareNode; import org.brixcms.web.tree.AbstractTreeModel; import org.brixcms.web.tree.JcrTreeNode; import org.brixcms.web.tree.NodeFilter; import org.brixcms.web.tree.TreeNode; import org.brixcms.web.util.AbstractModel; import org.brixcms.workspace.Workspace; public class HierarchicalNodeManagerPanel extends BrixGenericPanel<BrixNode> implements NodeTreeParentComponent { private static final String EDITOR_ID = "editor"; private static MetaDataKey<String> EDITOR_NODE_TYPE = new MetaDataKey<String>() { }; private final HierarchicalPluginLocator pluginLocator; private final IModel<Workspace> workspaceModel; private final BaseTree tree; private Component lastEditor; private Component editor; // used to detect whether workspace was changed between the requests (node // needs to be updated) // or node has been changed (workspace needs to be updated) private String oldWorkspaceId; public HierarchicalNodeManagerPanel(String id, IModel<Workspace> workspaceModel, IModel<BrixNode> rootNodeModel, HierarchicalPluginLocator pluginLocator) { super(id, rootNodeModel); this.workspaceModel = workspaceModel; this.pluginLocator = pluginLocator; add(tree = new Tree("tree", new TreeModel())); } @Override protected void onBeforeRender() { if (get("createNodesContainer") == null) { WebMarkupContainer createNodesContainer = new WebMarkupContainer("createNodesContainer") { @Override public boolean isVisible() { BrixNode folderNode = getNewNodeParent().getObject(); return pluginLocator.getPlugin().canAddNodeChild(folderNode, Context.ADMINISTRATION); } }; add(createNodesContainer); final NodeEditorPluginEntriesModel createNodesModel = new NodeEditorPluginEntriesModel(pluginLocator, getNewNodeParent()); createNodesContainer.add(new ListView<NodeEditorPluginEntry>("createNodes", createNodesModel) { @Override protected void populateItem(final ListItem<NodeEditorPluginEntry> item) { Link<Void> link; item.add(link = new Link<Void>("link") { @Override public void onClick() { NodeEditorPlugin plugin = item.getModelObject().getPlugin(); final Component currentEditor = getEditor(); // remember the last editor that is not a create // node // panel if (lastEditor == null || currentEditor.getMetaData(EDITOR_NODE_TYPE) == null) { lastEditor = currentEditor; } SimpleCallback goBack = new SimpleCallback() { @Override public void execute() { setupEditor(lastEditor); } }; IModel<BrixNode> parent = getNewNodeParent(plugin.getNodeType()); Panel panel = plugin.newCreateNodePanel(EDITOR_ID, parent, goBack); panel.setMetaData(EDITOR_NODE_TYPE, plugin.getNodeType()); setupEditor(panel); } @Override protected void onComponentTag(ComponentTag tag) { super.onComponentTag(tag); NodeEditorPlugin plugin = item.getModelObject().getPlugin(); String editorNodeType = getEditor().getMetaData(EDITOR_NODE_TYPE); if (plugin.getNodeType().equals(editorNodeType)) { CharSequence klass = tag.getAttribute("class"); if (klass == null) { klass = "active"; } else { klass = klass + " active"; } tag.put("class", klass); } } }); IModel<BrixNode> parent = getNewNodeParent(); NodeEditorPlugin plugin = item.getModelObject().getPlugin(); link.add(new Label("label", plugin.newCreateNodeCaptionModel(parent))); } }.setReuseItems(false)); editor = new WebMarkupContainer(EDITOR_ID); add(editor); setupDefaultEditor(); } Workspace workspace = workspaceModel.getObject(); BrixNode node = null; try { node = getModelObject(); } catch (JcrException e) { if (e.getCause() instanceof ItemNotFoundException) { HierarchicalNodePlugin dataPlugin = pluginLocator.getPlugin(); if (dataPlugin != null) { node = dataPlugin.getRootNode(workspace.getId()); getModel().setObject(null); selectNode(node); setupDefaultEditor(); tree.invalidateAll(); } } else { throw (e); } } if (node != null) { String nodeWorkspaceName = node.getSession().getWorkspace().getName(); if (!nodeWorkspaceName.equals(workspace.getId())) { // we have to either update node or workspace if (oldWorkspaceId != null && workspace.getId().equals(oldWorkspaceId)) { // the node changed, need to update the workspace Workspace newWorkspace = node.getBrix().getWorkspaceManager().getWorkspace(nodeWorkspaceName); workspaceModel.setObject(newWorkspace); } else { // the workspace has changed, update the node // 1 try to get node with same UUID, 2 try to get node with // same // path, 3 get root node JcrSession newSession = node.getBrix().getCurrentSession(workspace.getId()); String uuid = node.getIdentifier(); BrixNode newNode = JcrUtil.getNodeByUUID(newSession, uuid); if (newNode == null) { String path = node.getPath(); if (newSession.getRootNode().hasNode(path.substring(1))) { newNode = (BrixNode) newSession.getItem(path); } } if (newNode == null) { newNode = pluginLocator.getPlugin().getRootNode(workspaceModel.getObject().getId()); } selectNode(newNode); tree.invalidateAll(); tree.getTreeState().expandNode(((TreeModel) tree.getDefaultModelObject()).getRoot()); } } } super.onBeforeRender(); oldWorkspaceId = workspace.getId(); } private void setupEditor(Component newEditor) { editor.replaceWith(newEditor); editor = newEditor; } private void setupDefaultEditor() { setupEditor(new NodeEditorPanel(EDITOR_ID, getModel(), pluginLocator)); } private Component getEditor() { return get(EDITOR_ID); }; private IModel<BrixNode> getNewNodeParent() { return getNewNodeParent(null); } private IModel<BrixNode> getNewNodeParent(String nodeType) { return getNewNodeParent(nodeType, getModel()); }; private IModel<BrixNode> getNewNodeParent(String nodeType, IModel<BrixNode> currentModel) { BrixNode current = currentModel.getObject(); if (nodeType != null && current instanceof NodeChildFilter) { NodeChildFilter ncf = (NodeChildFilter) current; if (ncf.isNodeTypeAllowedAsChild(nodeType)) { return currentModel; } else { return getNewNodeParent(nodeType, new BrixNodeModel((BrixNode) current.getParent())); } } if (current.isFolder()) { return getModel(); } else { return new BrixNodeModel((BrixNode) current.getParent()); } }; private class Tree extends LinkTree { public Tree(String id, TreeModel model) { super(id, model); setLinkType(LinkType.REGULAR); getTreeState().expandNode(model.getRoot()); } @Override protected Component newNodeComponent(String id, IModel<Object> model) { JcrTreeNode node = (JcrTreeNode) model.getObject(); BrixNode n = node.getNodeModel().getObject(); Collection<NodeTreeRenderer> renderers = n.getBrix().getConfig().getRegistry().lookupCollection(NodeTreeRenderer.POINT); for (NodeTreeRenderer renderer : renderers) { Component component = renderer.newNodeComponent(id, Tree.this, model); if (component != null) { return component; } } return super.newNodeComponent(id, model); } @Override protected Component newJunctionLink(MarkupContainer parent, String id, Object node) { LinkType old = getLinkType(); setLinkType(LinkType.AJAX); Component c = super.newJunctionLink(parent, id, node); setLinkType(old); return c; } @SuppressWarnings("unchecked") @Override protected IModel getNodeTextModel(final IModel nodeModel) { return new AbstractModel<String>() { @Override public String getObject() { JcrTreeNode node = (JcrTreeNode) nodeModel.getObject(); BrixNode n = node.getNodeModel().getObject(); return n.getUserVisibleName(); } }; } @Override protected ITreeState newTreeState() { return new TreeState(); } }; private class TreeState extends DefaultTreeState { @Override public void selectNode(Object node, boolean selected) { if (selected) { JcrTreeNode n = (JcrTreeNode) node; HierarchicalNodeManagerPanel.this.setModel(n.getNodeModel()); setupDefaultEditor(); expandParents(n.getNodeModel().getObject()); } } private void expandParents(BrixNode node) { BrixNode parent = (BrixNode) node.getParent(); while (parent.getDepth() > 0) { expandNode(getTreeNode(parent)); parent = (BrixNode) parent.getParent(); } } @Override public boolean isNodeSelected(Object node) { JcrTreeNode n = (JcrTreeNode) node; IModel<BrixNode> model = n.getNodeModel(); return model != null && model.equals(HierarchicalNodeManagerPanel.this.getModel()); } @Override public Collection<Object> getSelectedNodes() { JcrTreeNode node = getTreeNode(getModelObject()); return Arrays.asList(new Object[] { node }); } }; private class TreeModel extends AbstractTreeModel { @Override public TreeNode getRoot() { Workspace workspace = workspaceModel.getObject(); return getTreeNode(pluginLocator.getPlugin().getRootNode(workspace.getId())); } }; public static final NodeFilter SHOW_ALL_NON_NULL_NODES_FILTER = new NodeFilter() { @Override public boolean isNodeAllowed(BrixNode node) { return node != null; } }; private JcrTreeNode getTreeNode(BrixNode node) { return TreeAwareNode.Util.getTreeNode(node, SHOW_ALL_NON_NULL_NODES_FILTER); } @Override public void selectNode(BrixNode node) { tree.getTreeState().selectNode(getTreeNode(node), true); } @Override public void updateTree() { tree.invalidateAll(); // tree.updateTree(); } }